package com.gitee.wsl.doc.html

import com.fleeksoft.ksoup.Ksoup
import com.fleeksoft.ksoup.parser.Parser.Companion.unescapeEntities
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


data class LinkTag(
    val rel: String,
    val href: String,
    val size: Int?,
)


object HTMLUtil {

    private val LINK_TAG_PATTERN = """<link\s+[^>]*rel=["']([a-z -]+)["'][^>]*>""".toRegex(RegexOption.IGNORE_CASE)
    private val REL_PATTERN = """\srel=("[^"]+"|'[^']+'|[\S]+)[\s/>]""".toRegex(RegexOption.IGNORE_CASE)
    private val HREF_PATTERN = """\shref=("[^"]+"|'[^']+'|[\S]+)[\s/>]""".toRegex(RegexOption.IGNORE_CASE)
    private val SIZES_PATTERN = """\ssizes=("[0-9]+x[0-9]+"|'[0-9]+x[0-9]+'|[\S]+)[\s/>]""".toRegex(RegexOption.IGNORE_CASE)

    fun findLinkTags(html: String, relevantRel: Set<String>): Sequence<LinkTag> =
        LINK_TAG_PATTERN.findAll(html)
            .mapNotNull { matchResult ->
                parseLinkTag(matchResult.value)
            }
            .filter { linkTag ->
                linkTag.rel in relevantRel
            }

    private fun parseLinkTag(tag: String): LinkTag? {
        return LinkTag(
            rel = parseAttribute(tag, REL_PATTERN)
                ?.lowercase()
                ?: return null,
            href = parseAttribute(tag, HREF_PATTERN)
                ?: return null,
            size = parseAttribute(tag, SIZES_PATTERN)
                ?.split('x')
                ?.get(0)
                ?.toIntOrNull(),
        )
    }

    private fun parseAttribute(tag: String, regex: Regex): String? =
        regex
            .find(tag)
            ?.groupValues
            ?.get(1)
            ?.trim('\'', '"')



    fun findFirstImageInHtml(
        text: String?,
        baseUrl: String?,
    ): ImageFromHTML? =
        if (text != null) {
            val doc =
                unescapeEntities(text, true).let {
                    Ksoup.parse(it,  baseUrl ?: "")
                }

            doc.getElementsByTag("img").asSequence()
                .filterNot { it.attr("width") == "1" || it.attr("height") == "1" }
                .map {
                    // abs: will resolve relative urls against the baseurl - and non-url value will get
                    // dropped, such as invalid values and data/base64 values
                    ImageFromHTML(
                        url = it.attr("abs:src"),
                        width = it.attr("width").toIntOrNull(),
                        height = it.attr("height").toIntOrNull(),
                    )
                }
                .firstOrNull {
                    it.url.isNotBlank() &&
                            !it.url.contains("twitter_icon", ignoreCase = true) &&
                            !it.url.contains("facebook_icon", ignoreCase = true)
                }
        } else {
            null
        }

    fun getFaviconUrl(html: String): String? {
        val doc = Ksoup.parse(html)

        val faviconLink = doc.select("link[rel~=(?i)^(shortcut|icon)$][href]").firstOrNull()

        return faviconLink?.attr("href")
    }

    fun getRssUrl(html: String): String? {
        val doc = Ksoup.parse(html)
        val queries = listOf(
            "link[type='application/rss+xml']",
            "link[type='application/atom+xml']",
            "link[type='application/json']",
            "link[type='application/feed+json']",
        )
        for (query in queries) {
            val rssElement = doc.select(query).firstOrNull()
            val rssUrl = rssElement?.attr("href")
            if (rssUrl != null) {
                return rssUrl
            }
        }
        return null
    }

}




/**
 * URL should be absolute at all times
 */
@Serializable
sealed class ThumbnailImage {
    abstract val url: String
    abstract val width: Int?
    abstract val height: Int?
    abstract val fromBody: Boolean
}

@Serializable
@SerialName("ImageFromHTML")
class ImageFromHTML(
    override val url: String,
    override val width: Int? = null,
    override val height: Int? = null,
) : ThumbnailImage() {
    override val fromBody: Boolean
        get() = true

    override fun equals(other: Any?): Boolean {
        return if (other is ImageFromHTML) {
            return url == other.url && width == other.width && height == other.height
        } else {
            false
        }
    }

    override fun hashCode(): Int {
        val prime = 31
        var result = 1
        result = prime * result + javaClass.simpleName.hashCode()
        result = prime * result + url.hashCode()
        result = prime * result + width.hashCode()
        result = prime * result + height.hashCode()
        return result
    }

    override fun toString(): String {
        return "ImageFromHTML(url='$url', width=$width, height=$height, fromBody=$fromBody)"
    }
}